package net.masker.bbtweet;

import java.io.IOException;
import java.io.InputStream;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.io.StreamConnection;

import net.rim.blackberry.api.browser.URLEncodedPostData;
import net.rim.blackberry.api.mail.Address;
import net.rim.blackberry.api.mail.Folder;
import net.rim.blackberry.api.mail.FolderNotFoundException;
import net.rim.blackberry.api.mail.Message;
import net.rim.blackberry.api.mail.Session;
import net.rim.blackberry.api.mail.Store;
import net.rim.blackberry.api.mail.event.MessageEvent;
import net.rim.blackberry.api.mail.event.MessageListener;
import net.rim.device.api.io.http.HttpProtocolConstants;
import net.rim.device.api.notification.NotificationsManager;
import net.rim.device.api.xml.parsers.*;
import org.w3c.dom.*;

class ConnectionManager extends Thread implements MessageListener
{
	private static Properties properties = Properties.fetch();
    private static String friendsTimelineURL = "http://twitter.com/statuses/friends_timeline.xml";
    private static String updateURL = "http://twitter.com/statuses/update.xml";
    
    
	private final boolean disabled()
    {    	
		if (properties.isBbtweetEnabled())
    	{
    		
    		properties.setConnectionManagerStarted(true);
    		properties.save();
    		return false;
    	}
    	else //shutdown
    	{
    		properties = Properties.fetch();
    		properties.setConnectionManagerStarted(false);
    		properties.save();
    		return true;
    	}    	
    }
    
    /**
	 * Parses XML stream, returning root element
	 * @param stream
	 * @return
	 */
    private final Element parseXML(InputStream stream)
	{
		
		try
		{
		//Parse input stream, get root element & normalize, returning root element
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.parse(stream);
        
        Element rootElement = document.getDocumentElement();
        rootElement.normalize();
        
        return rootElement;
        
		}
		catch (Exception e)
		{
			System.out.println(e.toString());
			return null;
		}
	}
    public void run()
    {
		// Only allow one Connection Manager thread
		properties = Properties.fetch();
		if (properties.isConnectionManagerStarted())
		{
			return;
		}
		
		for(;;)//ever
        {
			// Retrieve timeline if updates Enabled
			if (!disabled())
			{
				// Open stream and poll data
	            try
	            {
					// Create post data
	            	int lastID, newID;
					lastID = newID = properties.getLastID();
	            	URLEncodedPostData post = new URLEncodedPostData("", false);
					switch (lastID)
					{
						case -1: // initial run, pull lastID and store, don't deliver messages
							post.append("count", "1");
							break;
						case 0: // reset for testing, pull and deliver last 20 messages
							break;
						default: // pull all messages since lastID
							post.append("count", "200");
							post.append("since_id", Integer.toString(lastID));
					}
					
					// Create connection
	            	StreamConnection s = null;
	    			s = (StreamConnection)Connector.open(friendsTimelineURL + "?" + post.toString());
	                HttpConnection httpConn = (HttpConnection)s;
	                httpConn.setRequestProperty(HttpProtocolConstants.HEADER_AUTHORIZATION,"Basic "
	                		+ properties.getAuthenticationHash());
	
	                // Connect and check status
	                int status = httpConn.getResponseCode();         
	                if (status == HttpConnection.HTTP_OK)
	                {
	                	// Log success
	                	properties.setBbtweetStatusMessage("Retrieval: HTTP_OK 200");
	                	properties.save();
	                	
	                	// Create element, null connection
	                	Element root = parseXML(httpConn.openInputStream());
	                	httpConn = null;
	                	s = null;
	                	
						if (root != null) 
						{
							// Pull status list and clear root
							NodeList statusList = root.getElementsByTagName("status");
							root = null;
							
							// Process statuses
							// TODO Update to get all elements by tag name
							int numChildren = statusList.getLength();
							for (int i = numChildren - 1; i >= 0; --i) 
							{
								String[] Values = new String[18];
								int index = 0;
								for (int j = 0; j < statusList.item(i).getChildNodes().getLength(); ++j) 
								{
									// skip whitespace
									if (statusList.item(i).getChildNodes().item(j).getNodeType() != 1)
										continue;
									
									// pull values under user tag
									if ("user".equals(statusList.item(i).getChildNodes().item(j).getNodeName()))
									{
										NodeList userNodes = statusList.item(i).getChildNodes().item(j).getChildNodes();
										for (int k = 0; k < userNodes.getLength(); ++k)
										{
											// Skip whitespace
											if (userNodes.item(k).getNodeType() != 1)
												continue;
											
											// add values
											try
											{
												Values[index] = userNodes.item(k).getFirstChild().getNodeValue();
											}
											catch (NullPointerException e)
											{
												// null is OK
											}
											
											// increment values index
											index++;
										}
									}
									else
									{									
										try
										{
											Values[index] = statusList.item(i).getChildNodes().item(j).getFirstChild().getNodeValue();
										}
										catch (NullPointerException e)
										{
											// null is ok
										}
										
										// increment values index
										index++;
									}									
								}
								
								// push out update if new
								int thisID = Integer.parseInt(Values[1]);
								if (thisID > lastID)
								{
									if (thisID > newID)
										newID = thisID;
									
									// discard messages from user and messages on first run
									if ((Values[11].compareTo(properties.getUsername()) == 0) ||
											(lastID == -1))
										continue;
									
									// Open store and attempt to select "no service book" inbox
									Store store = Session.getDefaultInstance().getStore();
									Folder inbox;
									try
									{
										inbox = store.getFolder(2);
									}
									catch (FolderNotFoundException e)
									{
										// If that doesn't work, just grab the first inbox
										Folder[] folders = store.list(Folder.INBOX);
										inbox = folders[0];
									}
									
									// Create the message
									Message msg = new Message(inbox);
									try
									{
										// Create the message object
										Address from = new Address("http://bbtweet.googlecode.com/", "@" + Values[11]);
										msg.setFrom(from);
										msg.setContent(Values[2]);
										msg.setInbound(true);
										
										// Add message to inbox and set status to received
										inbox.appendMessage(msg);
										msg.setStatus(Message.Status.RX_RECEIVED, 0);
										
										// Add MessageListener to message
										msg.addMessageListener(this);
										
										// Trigger immediate notification
										NotificationsManager.triggerImmediateEvent(bbtweet.NOTIFICATIONS_ID_1, 0, this, null);										
									}
									catch (Exception e)
									{
										// Log exception
										properties.setBbtweetStatusMessage("Retrieval: " + e.toString());
							        	properties.save();
									}
								}
							}
							
							// Clean up
							statusList = null;
							
							// Log and update last ID
							properties.setBbtweetStatusMessage("Complete");
							properties.setLastID(newID);
							properties.save();
						}
	                	
	                }
	                else
	                {
	                	// Log failure
	                	properties.setBbtweetStatusMessage("Retrieval: " + httpConn.getResponseMessage());
	                	properties.save();
	                }
	            }
	            catch (IOException e) 
	            {
	            	// Log IO Exception
	            	properties.setBbtweetStatusMessage("Retrieval: " + e.toString());
	            	properties.save();
	            }
			}
			
            // Track time, checking occasionally for outbound status update
            int sleepTime = properties.getUpdateInterval() * 60;
            for (int i = 0; i < sleepTime; ++i)
            {            	
            	// Refresh properties
            	properties = Properties.fetch();
            	
            	// Send any outgoing status update
            	sendStatusUpdate();
            	
            	// Account for updated sleep time
            	int curSleepTime = properties.getUpdateInterval() * 60;
            	if (curSleepTime != sleepTime)
            	{
            		int diff = curSleepTime - sleepTime;
            		i = i - diff; // if update interval was increased i will decrease
            		sleepTime = curSleepTime;
            		continue; // re-evaluate for loop 
            	}
            	
            	// Sleep 
            	try
	            {
	            	sleep(1000);
	            }
	            catch (InterruptedException e) 
	            {
	                // Could not sleep
	            }
            }
        }
    }
	
	private final void sendStatusUpdate()
    {
    	//Return if nothing to do
    	if (properties.getPushStatusUpdate() != 1)
    		return;
    	
    	// Make user specified # of attempts to deliver message
    	for (int i = properties.getStatusUpdateRetryAttempts(); i > 0; --i)
    	{
			try
			{
				// Create post data
				URLEncodedPostData post = new URLEncodedPostData("", false);
				post.append("source", "bbtweet");
				post.append("status", properties.getStatusUpdate());
				StreamConnection s = (StreamConnection)Connector.open(updateURL + "?" + post.toString());
				HttpConnection httpConn = (HttpConnection)s;
	            httpConn.setRequestProperty(HttpProtocolConstants.HEADER_AUTHORIZATION,"Basic "
	            		+ properties.getAuthenticationHash());
	            httpConn.setRequestMethod("POST");
	            
	            // Connect and check status
	            int status = httpConn.getResponseCode();
	            if (status != HttpConnection.HTTP_OK)
	            {
	            	// Log failure and continue
                	properties.setBbtweetStatusMessage("Update: " + httpConn.getResponseMessage());
                	properties.save();
	            	continue;
	            }
	            else
	            {
	    			// Log success, update status, and return
	            	properties.setBbtweetStatusMessage("Update: HTTP_OK 200");
	            	properties.setPushStatusUpdate(2);
	            	properties.save();
	            	return;
	            }
	            
			}
			catch (IOException e) 
	        {
	        	// Log IO Exception
	        	properties.setBbtweetStatusMessage("Update: " + e.toString());
	        	properties.save();
	        }
    	}
    	
    	// Retry attempts exhausted, update status and return
    	properties.setPushStatusUpdate(3);
    	properties.save();
    	return;
    }

	// Message listener for changed messages
	public void changed(MessageEvent msgEvt)
	{
		// If a bbtweet message has been opened, stop notification
		try
		{
			NotificationsManager.cancelImmediateEvent(net.masker.bbtweet.bbtweet.NOTIFICATIONS_ID_1, 99,
					"bbtweet", "MessageOpened");
		}
		catch (Exception e)
		{
			// Exception can be raised if notification is not active, ignore
		}
	}
}